package com.libbase.datarepository

import com.libbase.Wrapper
import com.libbase.cast
import io.reactivex.rxjava3.disposables.CompositeDisposable
import io.reactivex.rxjava3.kotlin.addTo
import io.reactivex.rxjava3.subjects.PublishSubject

abstract class ListDataRepository<T : Any>(
    var data: MutableList<T>?,
    private val maxSize: Int? = null,
    private val distinct: Boolean = false
) : DataRepositoryInterface {
    override var dataChangeCallBack: ((encodeData: Any?) -> Unit)? = null

    override var itemChangeCallBack: ((encodeItem: Any?) -> Unit)? = null

    val dataObserver: PublishSubject<Wrapper<List<T>>> = PublishSubject.create()

    val itemObserver: PublishSubject<T> = PublishSubject.create()

    private val compositeDisposable = CompositeDisposable()

    init {
        dataObserver
            .subscribe({
                dataChangeCallBack?.let {
                    it(getEncodeData())
                }
            }, { error ->
                error.printStackTrace()
            })
            .addTo(compositeDisposable)

        itemObserver
            .subscribe({ item ->
                itemChangeCallBack?.let {
                    it(encode(item))
                }
            }, { error ->
                error.printStackTrace()
            })
            .addTo(compositeDisposable)
    }

    abstract fun decode(item: Any): T?

    abstract fun encode(item: T): Any

    abstract fun getKey(item: T): String

    private fun distinct(item: T) {
        if (!distinct) {
            return
        }

        data?.removeAll { it == item }
    }

    override fun setEncodeData(data: Any?) {
        val dataList: List<Any>? = cast(data)

        if (dataList == null) {
            this.data = null
        } else {
            this.data = dataList.mapNotNull { decode(it) }.toMutableList()
        }

        save()

        dataObserver.onNext(Wrapper(this.data))
    }

    override fun getEncodeData(): Any? {
        val data: MutableList<T> = this.data ?: return null

        return data.map { encode(it) }
    }

    override fun updateEncodeItem(item: Any) {
        data?.let { data ->
            val decodeItem = decode(item) ?: return

            val index = data.indexOfFirst { getKey(decodeItem) == getKey(it) }
            if (index == -1) {
                return
            }

            data[index] = decodeItem

            save()

            itemObserver.onNext(decodeItem)
        }
    }

    override fun addEncodeItem(item: Any) {
        val decodeItem: T = decode(item) ?: return

        distinct(decodeItem)

        val data = this.data
        if (data == null) {
            this.data = mutableListOf(decodeItem)
        } else {
            data.add(decodeItem)
        }

        maxSize?.let { maxSize ->
            this.data?.size?.let { size ->
                if (size > maxSize) {
                    this.data = this.data?.subList(size - maxSize, size)
                }
            }
        }

        save()

        dataObserver.onNext(Wrapper(this.data))
    }

    override fun insertEncodeItem(item: Any) {
        val decodeItem: T = decode(item) ?: return

        distinct(decodeItem)

        val data = this.data
        if (data == null) {
            this.data = mutableListOf(decodeItem)
        } else {
            data.add(0, decodeItem)
        }

        val maxSize: Int = this.maxSize ?: return

        val size: Int = this.data?.size ?: 0
        if (size > maxSize) {
            this.data = this.data?.subList(0, maxSize)
        }

        save()

        dataObserver.onNext(Wrapper(this.data))
    }

    override fun getEncodeItem(index: Int): Any? {
        val data = this.data ?: return null

        if (index < 0 || index >= data.size) {
            return null
        }

        return encode(data[index])
    }

    override fun getEncodeItemById(itemId: String): Any? {
        val item: T = data?.firstOrNull { getKey(it) == itemId } ?: return null
        return encode(item)
    }

    override fun removeItemById(itemId: String) {
        data?.removeAll { getKey(it) == itemId }

        save()

        dataObserver.onNext(Wrapper(this.data))
    }
}